#include <types.h>
#include <utils/io.h>
#include <utils/string.h>

// a GDT entry
struct gdt_entry {
    uint16 limit_low;
    uint16 base_low;
    uint8 base_mid;
    uint8 access;
    uint8 gran;
    uint8 base_hi;
} __attribute__((packed));

// GDT special pointer
struct gdt_ptr {
    uint16 limit;
    uint32 base;
} __attribute__((packed));

// IDT entry
struct idt_entry {
    uint16 base_low;
    uint16 selector;
    uint8  zero;
    uint8  flags;
    uint16 base_hi;
} __attribute__((packed));

// IDT special pointer
struct idt_ptr {
    uint16 limit;
    uint32 base;
} __attribute__((packed));

// GDT entries
struct gdt_entry gdt[5];
struct gdt_ptr gdt_ptr;

// IDT entries
struct idt_entry idt[256];
struct idt_ptr idt_ptr;

extern void gdt_install(uint32 gdt_ptr);
extern void idt_install(uint32 idt_ptr);

void gdt_set_entry(uint8 num, uint32 base, uint32 limit, uint8 access, uint8 gran) {
    gdt[num].base_low = base & 0xFFFF;
    gdt[num].base_mid = (base >> 16) & 0xFF;
    gdt[num].base_hi  = (base >> 24) & 0xFF;

    gdt[num].limit_low = limit & 0xFFFF;
    gdt[num].gran = ((limit >> 16) & 0x0F) | (gran & 0xF0);

    gdt[num].access = access;
}

void idt_set_entry(uint8 num, uint32 base, uint16 selector, uint8 flags) {
    idt[num].base_low = base & 0xFFFF;
    idt[num].base_hi  = (base >> 16) & 0xFFFF;

    idt[num].selector = selector;
    idt[num].zero = 0;

    idt[num].flags = flags;
}

void gdt_init() {
    gdt_ptr.limit = sizeof(struct gdt_entry) * 5 - 1;
    gdt_ptr.base = (uint32) &gdt;
    
    // null descriptor
    gdt_set_entry(0, 0, 0, 0, 0);

    // kernel code segment
    gdt_set_entry(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);

    // kernel data segment
    gdt_set_entry(2, 0, 0xFFFFFFFF, 0x92, 0xCF);

    // user code segment
    gdt_set_entry(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);

    // user data segment
    gdt_set_entry(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);
 
    gdt_install((uint32)&gdt_ptr);
}

extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();
extern void irq0();
extern void irq1();
extern void irq2();
extern void irq3();
extern void irq4();
extern void irq5();
extern void irq6();
extern void irq7();
extern void irq8();
extern void irq9();
extern void irq10();
extern void irq11();
extern void irq12();
extern void irq13();
extern void irq14();
extern void irq15();

#define IDT_ENTRY(n) idt_set_entry(n, (uint32)isr ## n, 0x08, 0x08E)
#define IRQ_ENTRY(n,i) idt_set_entry(n, (uint32)irq ## i, 0x08, 0x08E)
void idt_init() {
    idt_ptr.limit = sizeof(struct idt_entry) * 256 - 1;
    idt_ptr.base  = (uint32)&idt;

    memset(&idt, 0, sizeof(struct idt_entry) * 256);


    IDT_ENTRY(0);
    IDT_ENTRY(1);
    IDT_ENTRY(2);
    IDT_ENTRY(3);
    IDT_ENTRY(4);
    IDT_ENTRY(5);
    IDT_ENTRY(6);
    IDT_ENTRY(7);
    IDT_ENTRY(8);
    IDT_ENTRY(9);
    IDT_ENTRY(10);
    IDT_ENTRY(11);
    IDT_ENTRY(12);
    IDT_ENTRY(13);
    IDT_ENTRY(14);
    IDT_ENTRY(15);
    IDT_ENTRY(16);
    IDT_ENTRY(17);
    IDT_ENTRY(18);
    IDT_ENTRY(19);
    IDT_ENTRY(20);
    IDT_ENTRY(21);
    IDT_ENTRY(22);
    IDT_ENTRY(23);
    IDT_ENTRY(24);
    IDT_ENTRY(25);
    IDT_ENTRY(26);
    IDT_ENTRY(27);
    IDT_ENTRY(28);
    IDT_ENTRY(29);
    IDT_ENTRY(30);
    IDT_ENTRY(31);

    outb(0x20, 0x11);
    outb(0xA0, 0x11);
    outb(0x21, 0x20);
    outb(0xA1, 0x28);
    outb(0x21, 0x04);
    outb(0xA1, 0x02);
    outb(0x21, 0x01);
    outb(0xA1, 0x01);
    outb(0x21, 0x0);
    outb(0xA1, 0x0);

    IRQ_ENTRY(32, 0);
    IRQ_ENTRY(33, 1);
    IRQ_ENTRY(34, 2);
    IRQ_ENTRY(35, 3);
    IRQ_ENTRY(36, 4);
    IRQ_ENTRY(37, 5);
    IRQ_ENTRY(38, 6);
    IRQ_ENTRY(39, 7);
    IRQ_ENTRY(40, 8);
    IRQ_ENTRY(41, 9);
    IRQ_ENTRY(42, 10);
    IRQ_ENTRY(43, 11);
    IRQ_ENTRY(44, 12);
    IRQ_ENTRY(45, 13);
    IRQ_ENTRY(46, 14);
    IRQ_ENTRY(47, 15);

    idt_install((uint32)&idt_ptr);
}

void dt_init() {
    gdt_init();
    idt_init();
}
